// sdlmenu.c
#include <SDL3/SDL.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdbool.h>

#define WIDTH 1024
#define HEIGHT 768
#define PADDING 15
#define MAX_ITEMS 4000
#define MAX_INPUT_LEN 256
#define MAX_VISIBLE 10
#define FONT_SIZE 36
#define HINT_FONT_SIZE 20
#define FONT_PATH "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf"

typedef struct
{
    char text[512];
    char basename[256];
    bool is_match;
} MenuItem;

MenuItem items[MAX_ITEMS];
int item_count = 0;
char input_str[MAX_INPUT_LEN] = "";
size_t cursor = 0;
int selected_idx = 0;
int scroll_offset = 0;
bool running = true;
pid_t child_pid = -1;

SDL_Window *window = NULL;
SDL_Renderer *renderer = NULL;
FT_Library ft;
FT_Face main_face;
FT_Face hint_face;
SDL_Texture *main_glyphs[128] = {NULL};
SDL_Texture *hint_glyphs[128] = {NULL};
#define EMPTY_GLYPH_SENTINEL (SDL_Texture *)-1

const char *get_basename(const char *path);
int get_main_string_width(const char *str);
int get_hint_string_width(const char *str);
bool load_glyphs(FT_Face face, SDL_Texture **glyphs, int size);
bool init_freetype();
void draw_main_char(int x, int y, char c);
int draw_main_string(int x, int y, const char *str);
void draw_hint_char(int x, int y, char c);
int draw_hint_string(int x, int y, const char *str);
void read_items_from_stdin();
void filter_items();
void execute_command(const char *cmd);
void check_child_process();
void handle_input();
void render();
void cleanup();

const char *get_basename(const char *path)
{
    const char *slash = strrchr(path, '/');
    return (slash) ? (slash + 1) : path;
}

int get_main_string_width(const char *str)
{
    int width = 0;
    for (int i = 0; str[i]; i++)
    {
        FT_Load_Char(main_face, str[i], FT_LOAD_DEFAULT);
        width += main_face->glyph->advance.x >> 6;
    }
    return width;
}

int get_hint_string_width(const char *str)
{
    int width = 0;
    for (int i = 0; str[i]; i++)
    {
        FT_Load_Char(hint_face, str[i], FT_LOAD_DEFAULT);
        width += hint_face->glyph->advance.x >> 6;
    }
    return width;
}

bool load_glyphs(FT_Face face, SDL_Texture **glyphs, int size)
{
    FT_Set_Pixel_Sizes(face, 0, size);

    for (unsigned char c = 32; c <= 126; c++)
    {
        if (FT_Load_Char(face, c, FT_LOAD_RENDER))
        {
            fprintf(stderr, "ERROR: Missing glyph '%c' (size: %d)\n", c, size);
            return false;
        }

        FT_Bitmap *bitmap = &face->glyph->bitmap;

        if (bitmap->width == 0 || bitmap->rows == 0)
        {
            glyphs[c] = EMPTY_GLYPH_SENTINEL;
            continue;
        }

        SDL_Surface *surface = SDL_CreateSurface(
            bitmap->width, bitmap->rows, SDL_PIXELFORMAT_RGBA32);
        if (!surface)
        {
            fprintf(stderr, "ERROR: Surface failed for glyph '%c'\n", c);
            return false;
        }

        SDL_FillSurfaceRect(surface, NULL, 0x00000000);
        Uint32 *pixels = (Uint32 *)surface->pixels;
        for (int y = 0; y < bitmap->rows; y++)
        {
            for (int x = 0; x < bitmap->width; x++)
            {
                Uint8 alpha = bitmap->buffer[y * bitmap->pitch + x];
                if (alpha > 0)
                {
                    pixels[y * surface->w + x] = 0xFFFFFFFF;
                    pixels[y * surface->w + x] |= (alpha << 24);
                }
            }
        }

        glyphs[c] = SDL_CreateTextureFromSurface(renderer, surface);
        if (!glyphs[c])
        {
            fprintf(stderr, "ERROR: Texture failed for glyph '%c'\n", c);
            SDL_DestroySurface(surface);
            return false;
        }
        SDL_SetTextureBlendMode(glyphs[c], SDL_BLENDMODE_BLEND);
        SDL_DestroySurface(surface);
    }
    return true;
}

bool init_freetype()
{
    if (FT_Init_FreeType(&ft))
    {
        fprintf(stderr, "FreeType initialization failed\n");
        return false;
    }

    if (FT_New_Face(ft, FONT_PATH, 0, &main_face))
    {
        fprintf(stderr, "Could not load main font: %s\n", FONT_PATH);
        FT_Done_FreeType(ft);
        return false;
    }

    if (FT_New_Face(ft, FONT_PATH, 0, &hint_face))
    {
        fprintf(stderr, "Could not load hint font: %s\n", FONT_PATH);
        FT_Done_Face(main_face);
        FT_Done_FreeType(ft);
        return false;
    }

    if (!load_glyphs(main_face, main_glyphs, FONT_SIZE))
    {
        FT_Done_Face(hint_face);
        FT_Done_Face(main_face);
        FT_Done_FreeType(ft);
        return false;
    }

    if (!load_glyphs(hint_face, hint_glyphs, HINT_FONT_SIZE))
    {
        FT_Done_Face(hint_face);
        FT_Done_Face(main_face);
        FT_Done_FreeType(ft);
        return false;
    }

    return true;
}

void draw_main_char(int x, int y, char c)
{
    if (c < 32 || c > 126)
        return;
    if (main_glyphs[c] == EMPTY_GLYPH_SENTINEL)
        return;
    if (!main_glyphs[c])
        return;

    FT_Load_Char(main_face, c, FT_LOAD_DEFAULT);
    int x_off = main_face->glyph->bitmap_left;
    int y_off = main_face->glyph->bitmap_top;

    SDL_FRect dst = {
        (float)(x + x_off),
        (float)(y + (FONT_SIZE - y_off)),
        (float)main_face->glyph->bitmap.width,
        (float)main_face->glyph->bitmap.rows};
    SDL_RenderTexture(renderer, main_glyphs[c], NULL, &dst);
}

int draw_main_string(int x, int y, const char *str)
{
    int start_x = x;
    for (int i = 0; str[i]; i++)
    {
        draw_main_char(x, y, str[i]);
        FT_Load_Char(main_face, str[i], FT_LOAD_DEFAULT);
        x += main_face->glyph->advance.x >> 6;
    }
    return x - start_x;
}

void draw_hint_char(int x, int y, char c)
{
    if (c < 32 || c > 126)
        return;
    if (hint_glyphs[c] == EMPTY_GLYPH_SENTINEL)
        return;
    if (!hint_glyphs[c])
        return;

    FT_Load_Char(hint_face, c, FT_LOAD_DEFAULT);
    int x_off = hint_face->glyph->bitmap_left;
    int y_off = hint_face->glyph->bitmap_top;

    SDL_FRect dst = {
        (float)(x + x_off),
        (float)(y + (HINT_FONT_SIZE - y_off)),
        (float)hint_face->glyph->bitmap.width,
        (float)hint_face->glyph->bitmap.rows};
    SDL_RenderTexture(renderer, hint_glyphs[c], NULL, &dst);
}

int draw_hint_string(int x, int y, const char *str)
{
    int start_x = x;
    for (int i = 0; str[i]; i++)
    {
        draw_hint_char(x, y, str[i]);
        FT_Load_Char(hint_face, str[i], FT_LOAD_DEFAULT);
        x += hint_face->glyph->advance.x >> 6;
    }
    return x - start_x;
}

void read_items_from_stdin()
{
    char buf[512];
    item_count = 0;
    while (fgets(buf, sizeof(buf), stdin) && item_count < MAX_ITEMS)
    {
        size_t len = strlen(buf);
        while (len > 0 && isspace((unsigned char)buf[len - 1]))
            buf[--len] = '\0';

        strncpy(items[item_count].text, buf, sizeof(items[item_count].text) - 1);
        items[item_count].text[sizeof(items[item_count].text) - 1] = '\0';

        const char *basename = get_basename(buf);
        strncpy(items[item_count].basename, basename, sizeof(items[item_count].basename) - 1);
        items[item_count].basename[sizeof(items[item_count].basename) - 1] = '\0';

        items[item_count].is_match = true;
        item_count++;
    }
}

void filter_items()
{
    int input_len = strlen(input_str);
    if (input_len == 0)
    {
        for (int i = 0; i < item_count; i++)
            items[i].is_match = true;
        selected_idx = 0;
        scroll_offset = 0;
        return;
    }

    int match_count = 0;
    int first_match_idx = -1;
    for (int i = 0; i < item_count; i++)
    {
        const char *basename = items[i].basename;
        int basename_len = strlen(basename);

        bool match = (basename_len >= input_len);
        if (match)
        {
            for (int j = 0; j < input_len; j++)
            {
                if (tolower((unsigned char)input_str[j]) != tolower((unsigned char)basename[j]))
                {
                    match = false;
                    break;
                }
            }
        }

        items[i].is_match = match;
        if (match)
        {
            match_count++;
            if (first_match_idx == -1)
                first_match_idx = i;
        }
    }

    selected_idx = (match_count == 0) ? -1 : first_match_idx;
    int center_pos = MAX_VISIBLE / 2;
    scroll_offset = selected_idx - center_pos;
    if (scroll_offset < 0)
        scroll_offset = 0;
}

void execute_command(const char *cmd)
{
    if (!cmd || strlen(cmd) == 0 || child_pid != -1)
        return;

    pid_t pid = fork();
    if (pid == 0)
    {
        char *args[] = {(char *)cmd, NULL};
        freopen("/dev/null", "r", stdin);
        freopen("/dev/null", "w", stdout);
        freopen("/dev/null", "w", stderr);
        execvp(args[0], args);
        exit(EXIT_FAILURE);
    }
    else if (pid > 0)
    {
        child_pid = pid;
    }
}

void check_child_process()
{
    if (child_pid == -1)
        return;

    int status;
    pid_t result = waitpid(child_pid, &status, WNOHANG);
    if (result == child_pid || result == -1)
    {
        child_pid = -1;
        running = false;
    }
}

void handle_input()
{
    SDL_Event event;
    bool ctrl_pressed = false;

    while (SDL_PollEvent(&event))
    {
        switch (event.type)
        {
        case SDL_EVENT_QUIT:
            running = false;
            break;

        case SDL_EVENT_KEY_DOWN:
            ctrl_pressed = (event.key.mod & SDL_KMOD_CTRL) != 0;

            if (event.key.key == SDLK_ESCAPE)
            {
                running = false;
                return;
            }
            else if (event.key.key == SDLK_RETURN)
            {
                const char *cmd = NULL;
                if (selected_idx != -1 && items[selected_idx].is_match)
                    cmd = items[selected_idx].text;
                else if (strlen(input_str) > 0)
                    cmd = input_str;
                if (cmd)
                    execute_command(cmd);
            }
            // Use SDLK_H (uppercase) instead of SDLK_h
            else if ((event.key.key == SDLK_BACKSPACE || (ctrl_pressed && event.key.key == SDLK_H)) && cursor > 0)
            {
                cursor--;
                memmove(&input_str[cursor], &input_str[cursor + 1], MAX_INPUT_LEN - cursor - 1);
                filter_items();
            }
            // Use SDLK_D instead of SDLK_d
            else if ((event.key.key == SDLK_DELETE || (ctrl_pressed && event.key.key == SDLK_D)) && cursor < strlen(input_str))
            {
                memmove(&input_str[cursor], &input_str[cursor + 1], MAX_INPUT_LEN - cursor - 1);
                filter_items();
            }
            // Use SDLK_K instead of SDLK_k
            else if (ctrl_pressed && event.key.key == SDLK_K)
            {
                input_str[cursor] = '\0';
                filter_items();
            }
            // Use SDLK_U instead of SDLK_u
            else if (ctrl_pressed && event.key.key == SDLK_U)
            {
                input_str[0] = '\0';
                cursor = 0;
                filter_items();
            }
            // Use SDLK_N instead of SDLK_n
            else if (event.key.key == SDLK_DOWN || (ctrl_pressed && event.key.key == SDLK_N))
            {
                if (selected_idx == -1)
                    return;
                int next_idx = selected_idx + 1;
                while (next_idx < item_count && !items[next_idx].is_match)
                    next_idx++;
                if (next_idx < item_count)
                {
                    selected_idx = next_idx;
                    int center_pos = MAX_VISIBLE / 2;
                    scroll_offset = selected_idx - center_pos;
                    if (scroll_offset < 0)
                        scroll_offset = 0;
                }
            }
            // Use SDLK_P instead of SDLK_p
            else if (event.key.key == SDLK_UP || (ctrl_pressed && event.key.key == SDLK_P))
            {
                if (selected_idx == -1)
                    return;
                int prev_idx = selected_idx - 1;
                while (prev_idx >= 0 && !items[prev_idx].is_match)
                    prev_idx--;
                if (prev_idx >= 0)
                {
                    selected_idx = prev_idx;
                    int center_pos = MAX_VISIBLE / 2;
                    scroll_offset = selected_idx - center_pos;
                    if (scroll_offset < 0)
                        scroll_offset = 0;
                }
            }
            // Use SDLK_F instead of SDLK_f
            else if (event.key.key == SDLK_RIGHT || (ctrl_pressed && event.key.key == SDLK_F))
            {
                if (cursor < strlen(input_str))
                    cursor++;
            }
            // Use SDLK_B instead of SDLK_b
            else if (event.key.key == SDLK_LEFT || (ctrl_pressed && event.key.key == SDLK_B))
            {
                if (cursor > 0)
                    cursor--;
            }
            // Use SDLK_A instead of SDLK_a
            else if (event.key.key == SDLK_HOME || (ctrl_pressed && event.key.key == SDLK_A))
            {
                cursor = 0;
            }
            // Use SDLK_E instead of SDLK_e
            else if (event.key.key == SDLK_END || (ctrl_pressed && event.key.key == SDLK_E))
            {
                cursor = strlen(input_str);
            }
            break;

        case SDL_EVENT_MOUSE_WHEEL:
        {
            int center_pos = MAX_VISIBLE / 2;
            if (event.wheel.y > 0)
            {
                if (selected_idx == -1)
                    return;
                int prev_idx = selected_idx - 1;
                while (prev_idx >= 0 && !items[prev_idx].is_match)
                    prev_idx--;
                if (prev_idx >= 0)
                {
                    selected_idx = prev_idx;
                    scroll_offset = selected_idx - center_pos;
                    if (scroll_offset < 0)
                        scroll_offset = 0;
                }
            }
            else if (event.wheel.y < 0)
            {
                if (selected_idx == -1)
                    return;
                int next_idx = selected_idx + 1;
                while (next_idx < item_count && !items[next_idx].is_match)
                    next_idx++;
                if (next_idx < item_count)
                {
                    selected_idx = next_idx;
                    scroll_offset = selected_idx - center_pos;
                    if (scroll_offset < 0)
                        scroll_offset = 0;
                }
            }
            break;
        }

        case SDL_EVENT_TEXT_INPUT:
            if (strlen(input_str) < MAX_INPUT_LEN - 1 && event.text.text[0] >= 32 && event.text.text[0] <= 126)
            {
                memmove(&input_str[cursor + 1], &input_str[cursor], MAX_INPUT_LEN - cursor - 1);
                input_str[cursor++] = event.text.text[0];
                filter_items();
            }
            break;
        }
    }
}

void render()
{
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
    SDL_RenderClear(renderer);

    SDL_FRect input_rect = {
        (float)PADDING,
        (float)PADDING,
        (float)(WIDTH - 2 * PADDING),
        (float)(FONT_SIZE + 12)};
    SDL_SetRenderDrawColor(renderer, 50, 50, 50, 255);
    SDL_RenderFillRect(renderer, &input_rect);

    const char *prompt_text = "Filter > ";
    int prompt_x = PADDING + 8;
    int prompt_y = PADDING + 6;
    draw_main_string(prompt_x, prompt_y, prompt_text);
    int prompt_width = get_main_string_width(prompt_text);

    int input_x = prompt_x + prompt_width;
    draw_main_string(input_x, prompt_y, input_str);

    int cursor_x = input_x;
    for (size_t i = 0; i < cursor; i++)
    {
        FT_Load_Char(main_face, input_str[i], FT_LOAD_DEFAULT);
        cursor_x += main_face->glyph->advance.x >> 6;
    }
    SDL_FRect cursor_rect = {
        (float)cursor_x,
        (float)prompt_y,
        2.0f,
        (float)FONT_SIZE};
    SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
    SDL_RenderFillRect(renderer, &cursor_rect);

    int separator_y = PADDING + FONT_SIZE + 20;
    SDL_SetRenderDrawColor(renderer, 100, 100, 100, 255);
    SDL_RenderLine(renderer,
                   (float)PADDING, (float)separator_y,
                   (float)(WIDTH - PADDING), (float)separator_y);

    int y_offset = separator_y + 10;
    int visible_count = 0;
    int match_count = 0;
    for (int i = 0; i < item_count; i++)
        if (items[i].is_match)
            match_count++;

    for (int i = 0; i < item_count && visible_count < MAX_VISIBLE; i++)
    {
        if (items[i].is_match)
        {
            if (i < scroll_offset)
                continue;

            if (i == selected_idx)
            {
                SDL_SetRenderDrawColor(renderer, 0, 85, 119, 255);
                SDL_FRect highlight_rect = {
                    (float)PADDING,
                    (float)(y_offset - 2),
                    (float)(WIDTH - 2 * PADDING),
                    (float)(FONT_SIZE + 8)};
                SDL_RenderFillRect(renderer, &highlight_rect);
            }

            draw_main_string(PADDING + 10, y_offset, items[i].text);
            y_offset += FONT_SIZE + 12;
            visible_count++;
        }
    }

    if (match_count > 0)
    {
        const char *hint_text = "Controls: PgUp/PgDn = Nav | Ctrl+N/P = Nav | Enter = Run | Esc = Quit";
        int hint_x = PADDING + 10;
        int hint_y = y_offset + 10;
        draw_hint_string(hint_x, hint_y, hint_text);
    }

    SDL_RenderPresent(renderer);
}

void cleanup()
{
    SDL_StopTextInput(window);

    for (int c = 32; c <= 126; c++)
    {
        if (main_glyphs[c] != NULL && main_glyphs[c] != EMPTY_GLYPH_SENTINEL)
            SDL_DestroyTexture(main_glyphs[c]);
    }

    for (int c = 32; c <= 126; c++)
    {
        if (hint_glyphs[c] != NULL && hint_glyphs[c] != EMPTY_GLYPH_SENTINEL)
            SDL_DestroyTexture(hint_glyphs[c]);
    }

    FT_Done_Face(hint_face);
    FT_Done_Face(main_face);
    FT_Done_FreeType(ft);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
}

int main()
{
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0)
    {
        fprintf(stderr, "SDL_Init failed: %s\n", SDL_GetError());
        return 1;
    }

    window = SDL_CreateWindow("sdlmenu", WIDTH, HEIGHT, 0);
    if (!window)
    {
        fprintf(stderr, "Window failed: %s\n", SDL_GetError());
        SDL_Quit();
        return 1;
    }

    renderer = SDL_CreateRenderer(window, NULL);
    if (!renderer)
    {
        fprintf(stderr, "Renderer failed: %s\n", SDL_GetError());
        SDL_DestroyWindow(window);
        SDL_Quit();
        return 1;
    }

    SDL_StartTextInput(window);

    if (!init_freetype())
    {
        cleanup();
        return 1;
    }

    read_items_from_stdin();
    filter_items();

    while (running)
    {
        handle_input();
        check_child_process();
        render();
        SDL_Delay(16);
    }

    cleanup();
    return 0;
}